package com.zbs.framework.swagger.service.impl;

import com.zbs.framework.swagger.config.Docket;
import com.zbs.framework.swagger.config.SwaggerBusinessRuntimeException;
import com.zbs.framework.swagger.config.SwaggerPropertySetting;
import com.zbs.framework.swagger.constants.SwaggerApiConstant;
import com.zbs.framework.swagger.controller.params.ApiCheckedParam;
import com.zbs.framework.swagger.entity.*;
import com.zbs.framework.swagger.entity.doc.GroupApi;
import com.zbs.framework.swagger.entity.doc.GroupApiParam;
import com.zbs.framework.swagger.entity.doc.GroupDoc;
import com.zbs.framework.swagger.entity.doc.GroupModel;
import com.zbs.framework.swagger.entity.eolink.BaseInfoModel;
import com.zbs.framework.swagger.entity.eolink.EolinkApiModel;
import com.zbs.framework.swagger.entity.eolink.EolinkModel;
import com.zbs.framework.swagger.entity.eolink.ParamInfo;
import com.zbs.framework.swagger.service.SwaggerApiService;
import com.zbs.framework.swagger.utils.FiledUtils;
import com.zbs.framework.swagger.utils.JdbcUtils;
import com.zbs.framework.swagger.utils.SnowFlakeUtil;
import com.zbs.framework.swagger.view.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SwaggerApiServiceImpl implements SwaggerApiService, ApplicationContextAware {

    private Docket docket;
    @Autowired
    private JdbcUtils jdbcUtils;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext){
        Docket d = null;
        try {
            d = applicationContext.getBean(Docket.class);
        } catch (BeansException e) {
            log.info("Docket init");
        }
        if (d != null) {
            this.docket = d;
        } else {
            this.docket = new Docket();
        }

    }

    @Override
    public List<TablesView> getTablesViewList() {
        if (SwaggerPropertySetting.enableApi && CollectionUtils.isEmpty(SwaggerApiConstant.tablesView)) {
            try {
                String tableSql = "select * from information_schema.`tables` as tb where table_schema=?";
                String columnsSql = "select * from information_schema.`columns` as tb where table_schema= ? and table_name = ? ORDER BY ordinal_position";
                JdbcTemplate jdbcTemplate = new JdbcTemplate(jdbcUtils.getDataSource());
                AtomicReference<Integer> id = new AtomicReference<>(0);
                SwaggerApiConstant.tablesView = jdbcTemplate.query(tableSql,  new BeanPropertyRowMapper<>(TablesVo.class),jdbcUtils.getDBName()).stream().map(tablesVo -> {
                    id.getAndSet(id.get() + 1);
                    List<ColumnsView> collect = jdbcTemplate.query(columnsSql,new BeanPropertyRowMapper<>(ColumnsVo.class),jdbcUtils.getDBName(), tablesVo.getTableName()).stream().map(ColumnsView::new).collect(Collectors.toList());
                    return new TablesView(id.get(),tablesVo, collect);
                }).collect(Collectors.toList());
            } catch (Exception e) {
                e.printStackTrace();
                throw new SwaggerBusinessRuntimeException(-1, "数据源异常，请检查配置！");
            }
        }
        return SwaggerApiConstant.tablesView;
    }

    @Override
    public EolinkModel exportEolinkApi(ApiCheckedParam param) {
        String groupName = param.getGroupName();
        List<View> viewNewList = this.getApiModel();
        List<Integer> idList = param.getIdList().stream().distinct().collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(viewNewList)) {
            Long groupID1 = SnowFlakeUtil.getId();
            int level = 1;
            List<EolinkModel> eolinkModels = new ArrayList<>();
            viewNewList.forEach(view -> {
                if (idList.contains(view.getId())) {
                    String groupName1 = view.getGroupName();
                    String groupPath1 = String.format("%s,%s", groupID1, view.getId());
                    long parentGroupID1 = view.getId().longValue();
                    List<EolinkModel> eolinkModels1 = new ArrayList<>();
                    view.getApiList().forEach(action -> {
                        if (idList.contains(action.getId())) {
                            String groupName2 = action.getApiName();
                            String groupPath2 = String.format("%s,%s", groupPath1, view.getId());
                            long parentGroupID2 = action.getId().longValue();
                            List<EolinkApiModel> apiList = new ArrayList<>();
                            if (!CollectionUtils.isEmpty(action.getApiList())) {
                                action.getApiList().forEach(api -> {
                                    if (idList.contains(api.getId())) {
                                        String apiName = api.getApiName();
                                        String path = api.getPath();
                                        String requestType = api.getRequestType();
                                        String method = api.getMethod();
                                        BaseInfoModel baseInfoModel = BaseInfoModel.build(apiName, path, this.getMethodEolinkType(method), this.getRequestParamType(requestType));
                                        List<RequestParams> requestParamsList = api.getRequestParamsList();
                                        List<RequestParams> responseParamsList = api.getResponseParamsList();
                                        List<ParamInfo> requestInfo = new ArrayList<>();
                                        List<ParamInfo> resultInfo = new ArrayList<>();
                                        this.recursive(requestParamsList, requestInfo);
                                        this.recursive(responseParamsList, resultInfo);
                                        EolinkApiModel eolinkApiModel = EolinkApiModel.build(groupName2, baseInfoModel, requestInfo, resultInfo);
                                        apiList.add(eolinkApiModel);
                                    }
                                });
                            }
                            EolinkModel eolinkModel = EolinkModel.build(parentGroupID2, groupName2, parentGroupID1, groupPath2, level + 2, null, apiList);
                            eolinkModels1.add(eolinkModel);
                        }
                    });
                    EolinkModel eolinkModel = EolinkModel.build(parentGroupID1, groupName1, groupID1, groupPath1, level + 1, eolinkModels1, null);
                    eolinkModels.add(eolinkModel);
                }
            });
            return EolinkModel.build(groupID1, groupName, 0L, groupID1.toString(), level, eolinkModels, null);
        }
        return null;
    }

    private void recursive(List<RequestParams> paramsList, List<ParamInfo> info) {
        paramsList.forEach(params -> {
            ParamInfo paramInfo = new ParamInfo(params);
            List<ParamInfo> children = new ArrayList<>();
            if (!CollectionUtils.isEmpty(params.getChildren())) {
                this.recursive(params.getChildren(), children);
                paramInfo.setChildList(children);
            }
            info.add(paramInfo);
        });

    }

    @Override
    public List<ViewNew> getApiListV2() {
        return this.getApiModel().stream().map(ViewNew::new).collect(Collectors.toList());
    }

    private Integer getMethodEolinkType(String method) {
        switch (method) {
            case "GET":
                return 1;
            case "POST":
                return 0;
            case "PUT":
                return 2;
            case "DELETE":
                return 3;
            default:
                return 0;

        }
    }

    @Override
    public List<GroupDoc> exportDocApi(ApiCheckedParam param) {
        return this.exportApi(param);
    }


    private List<GroupDoc> exportApi(ApiCheckedParam param) {
        List<View> viewList = this.getApiModel();
        List<Integer> idList = param.getIdList().stream().distinct().collect(Collectors.toList());
        List<GroupDoc> groupDocs = new ArrayList<>();
        viewList.forEach(view -> {
            if (!CollectionUtils.isEmpty(view.getApiList())) {
                if (idList.contains(view.getId())) {
                    List<GroupModel> groupModelList = new ArrayList<>();
                    view.getApiList().forEach(apiModelView -> {
                        if (!CollectionUtils.isEmpty(apiModelView.getApiList())) {
                            if (idList.contains(apiModelView.getId())) {
                                List<GroupApi> apiLis = new ArrayList<>();
                                apiModelView.getApiList().forEach(apiView -> {
                                    if (idList.contains(apiView.getId())) {
                                        List<RequestParams> requestParamsList = apiView.getRequestParamsList();
                                        List<RequestParams> responseParamsList = apiView.getResponseParamsList();
                                        List<GroupApiParam> reqParams = new ArrayList<>();
                                        List<GroupApiParam> respParams = new ArrayList<>();
                                        this.recursiveParams(reqParams, requestParamsList, null);
                                        this.recursiveParams(respParams, responseParamsList, null);
                                        GroupApi groupApi = new GroupApi(apiView, reqParams, respParams);
                                        apiLis.add(groupApi);
                                    }
                                });
                                GroupModel groupModel = new GroupModel(apiModelView, apiLis);
                                groupModelList.add(groupModel);
                            }
                        }
                    });
                    GroupDoc groupDoc = new GroupDoc(view, groupModelList);
                    groupDocs.add(groupDoc);
                }
            }
        });
        return groupDocs;
    }

    private void recursiveParams(List<GroupApiParam> params, List<RequestParams> paramsList, String apiName) {
        if (!CollectionUtils.isEmpty(paramsList)) {
            paramsList.forEach(param -> {
                GroupApiParam groupApiParam = new GroupApiParam(param, apiName);
                params.add(groupApiParam);
                if (!CollectionUtils.isEmpty(param.getChildren())) {
                    this.recursiveParams(params, param.getChildren(), param.getName());
                }
            });
        }
    }


    private Integer getRequestParamType(String str) {
        switch (str) {
            case "JSON":
                return 2;
            case "RESTFUL":
            case "FORM_DATA":
            default:
                return 0;
        }
    }


    @Override
    public List<ErrorStatus> getErrorStatusList() {
        if (docket.getErrorMsgMap() != null) {
            return docket.getErrorMsgMap().keySet().stream()
                    .map(k -> new ErrorStatus(docket.getErrorMsgMap().get(k), k))
                    .collect(Collectors.toList());
        }
        return null;
    }

    @Override
    public List<View> getApiModel() {
        if (SwaggerPropertySetting.enableApi) {
            List<View> viewList = SwaggerApiConstant.apiViewMap.get(SwaggerApiConstant.view);
            if (!CollectionUtils.isEmpty(viewList)) {
                return viewList;
            }
            final List<ApiEntityModel> apiEntityModelList = SwaggerApiConstant.apiModelList;
            Map<String, List<ApiModelView>> map = new HashMap<>();
            AtomicInteger id = new AtomicInteger(1);
            apiEntityModelList.forEach(apiEntityModel -> {
                if (!CollectionUtils.isEmpty(apiEntityModel.getApiList())) {
                    apiEntityModel.setId(id.get());
                    id.set(id.get() + 1);
                    List<ApiModelView> list = new ArrayList<>();
                    if (map.containsKey(apiEntityModel.getGroup())) {
                        list = map.get(apiEntityModel.getGroup());
                    }
                    List<ApiView> collect = apiEntityModel.getApiList().stream().sorted(Comparator.comparing(Api::getSort)).map(api -> {
                        StringBuilder stringBuilder = new StringBuilder();
                        if (apiEntityModel.getPath() != null && apiEntityModel.getPath().length != 0) {
                            stringBuilder.append("/").append(apiEntityModel.getPath()[0]);
                        }
                        if (api.getPath().length != 0) {
                            stringBuilder.append("/")
                                    .append(api.getPath()[0]);
                        }

                        String path = getPath(stringBuilder.toString());
                        ApiView build = ApiView.builder().apiName(api.getApiName())
                                .id(id.get())
                                .path(path)
                                .method(api.getMethod())
                                .requestType(api.getRequestType())
                                .requestParamsList(getReq(null, api.getRequestParamsList()))
                                .responseIsArray(api.isResponseIsArray())
                                .responseParamsList(getResp(api.getResponseParamsList(), api.isResponseIsArray()))
                                .build();
                        SwaggerApiConstant.idList.add(id.get());
                        id.set(id.get() + 1);
                        return build;
                    }).collect(Collectors.toList());
                    ApiModelView build = ApiModelView.builder()
                            .apiList(collect)
                            .id(id.get())
                            .apiName(apiEntityModel.getApiName())
                            .build();
                    SwaggerApiConstant.idList.add(id.get());
                    id.set(id.get() + 1);
                    list.add(build);
                    map.put(apiEntityModel.getGroup(), list);
                }
            });
            List<View> collect = map.keySet().stream().map(key -> {
                View build = View.builder().id(id.get()).groupName(key).apiList(map.get(key)).build();
                SwaggerApiConstant.idList.add(id.get());
                id.set(id.get() + 1);
                return build;
            }).peek(view -> {
                view.setParentId(0);
                view.setApiList(view.getApiList().stream().peek(apiModelView -> {
                    apiModelView.setParentId(view.getId());
                    apiModelView.setApiList(apiModelView.getApiList().stream().peek(apiView -> {
                        apiView.setParentId(apiModelView.getId());
                    }).collect(Collectors.toList()));
                }).collect(Collectors.toList()));

            }).collect(Collectors.toList());
            SwaggerApiConstant.apiViewMap.put(SwaggerApiConstant.view, collect);
            return collect;
        }
        return new ArrayList<>();
    }

    private String getPath(String path) {
        path = path.replaceAll("//", "/");
        if (path.contains("//")) {
            path = this.getPath(path);
        }
        return path;
    }

    private List<RequestParams> getResp(List<RequestParams> requestParamsList, boolean responseIsArray) {
        AtomicLong i = new AtomicLong(1);
        AtomicLong id = new AtomicLong();
        if (docket.getClazz() != null && docket.getDataFiled() != null) {
            List<RequestParams> field = FiledUtils.getField(docket.getClazz());
            field.forEach(p -> {
                long andIncrement = i.getAndIncrement();
                if (p.getName().equals(docket.getDataFiled())) {
                    id.set(andIncrement);
                }
            });
        }
        if (!CollectionUtils.isEmpty(requestParamsList)) {
            getReq(id.get(), requestParamsList);
        }
        AtomicLong num = new AtomicLong(1);
        if (docket.getClazz() != null && docket.getDataFiled() != null) {
            List<RequestParams> field = FiledUtils.getField(docket.getClazz());
            return field.stream().peek(request -> {
                request.setId(num.getAndIncrement());
                if (request.getName().equals(docket.getDataFiled())) {
                    request.setId(id.get()).setChildren(null).setArray(responseIsArray);
                    if (!CollectionUtils.isEmpty(requestParamsList)) {
                        request.setChildren(requestParamsList);
                    }
                }
            }).collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(requestParamsList)) return new ArrayList<>();
        return requestParamsList;
    }

    private List<RequestParams> getReq(Long i, List<RequestParams> requestParamsList) {
        if (CollectionUtils.isEmpty(requestParamsList)) return new ArrayList<>();
        AtomicLong num = new AtomicLong(1);
        requestParamsList.forEach(params -> {
            long andIncrement = num.getAndIncrement();
            params.setId(andIncrement);
            if (i != null) {
                params.setId(Long.parseLong(i + "" + andIncrement));
            }
            if (!CollectionUtils.isEmpty(params.getChildren())) {
                this.get(params, params.getChildren());
            }
        });
        return requestParamsList;
    }

    private void get(RequestParams p, List<RequestParams> requestParamsList) {
        AtomicInteger num = new AtomicInteger(1);
        requestParamsList.forEach(params -> {
            Long id = p.getId();
            int andIncrement = num.getAndIncrement();
            params.setId(Long.parseLong(id + "" + andIncrement));
            if (!CollectionUtils.isEmpty(params.getChildren())) {
                this.get(params, params.getChildren());
            }
        });
    }
}
